/*-
 * Copyright (c) 2012,2013 Kai Wang
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "ld.h"
#include "ld_arch.h"
#include "ld_dynamic.h"
#include "ld_input.h"
#include "ld_output.h"
#include "ld_reloc.h"
#include "ld_symbols.h"
#include "ld_utils.h"
#include "i386.h"

ELFTC_VCSID("$Id: i386.c 2967 2013-10-12 23:58:13Z kaiwang27 $");

static void _create_plt_reloc(struct ld *ld, struct ld_symbol *lsb,
    uint64_t offset);
static void _create_got_reloc(struct ld *ld, struct ld_symbol *lsb,
    uint64_t type, uint64_t offset);
static void _create_copy_reloc(struct ld *ld, struct ld_symbol *lsb);
static void _create_dynamic_reloc(struct ld *ld, struct ld_input_section *is,
    struct ld_symbol *lsb, uint64_t type, uint64_t offset);
static void _scan_reloc(struct ld *ld, struct ld_input_section *is,
    struct ld_reloc_entry *lre);
static struct ld_input_section *_find_and_create_got_section(struct ld *ld,
    int create);
static struct ld_input_section *_find_and_create_gotplt_section(struct ld *ld,
    int create);
static struct ld_input_section *_find_and_create_plt_section(struct ld *ld,
    int create);
static uint64_t _get_max_page_size(struct ld *ld);
static uint64_t _get_common_page_size(struct ld *ld);
static void _process_reloc(struct ld *ld, struct ld_input_section *is,
    struct ld_reloc_entry *lre, struct ld_symbol *lsb, uint8_t *buf);
static const char *_reloc2str(uint64_t r);
static void _reserve_got_entry(struct ld *ld, struct ld_symbol *lsb, int num);
static void _reserve_gotplt_entry(struct ld *ld, struct ld_symbol *lsb);
static void _reserve_plt_entry(struct ld *ld, struct ld_symbol *lsb);
static int _is_absolute_reloc(uint64_t r);
static int _is_relative_reloc(uint64_t r);
static void _warn_pic(struct ld *ld, struct ld_reloc_entry *lre);
static uint32_t _got_offset(struct ld *ld, struct ld_symbol *lsb);

static uint64_t
_get_max_page_size(struct ld *ld)
{

	(void) ld;
	return (0x1000);
}

static uint64_t
_get_common_page_size(struct ld *ld)
{

	(void) ld;
	return (0x1000);
}

static const char *
_reloc2str(uint64_t r)
{
	static char s[32];

	switch (r) {
		case 0: return "R_386_NONE";
		case 1: return "R_386_32";
		case 2: return "R_386_PC32";
		case 3: return "R_386_GOT32";
		case 4: return "R_386_PLT32";
		case 5: return "R_386_COPY";
		case 6: return "R_386_GLOB_DAT";
		case 7: return "R_386_JMP_SLOT";
		case 8: return "R_386_RELATIVE";
		case 9: return "R_386_GOTOFF";
		case 10: return "R_386_GOTPC";
		case 14: return "R_386_TLS_TPOFF";
		case 15: return "R_386_TLS_IE";
		case 16: return "R_386_TLS_GOTI";
		case 17: return "R_386_TLS_LE";
		case 18: return "R_386_TLS_GD";
		case 19: return "R_386_TLS_LDM";
		case 24: return "R_386_TLS_GD_32";
		case 25: return "R_386_TLS_GD_PUSH";
		case 26: return "R_386_TLS_GD_CALL";
		case 27: return "R_386_TLS_GD_POP";
		case 28: return "R_386_TLS_LDM_32";
		case 29: return "R_386_TLS_LDM_PUSH";
		case 30: return "R_386_TLS_LDM_CALL";
		case 31: return "R_386_TLS_LDM_POP";
		case 32: return "R_386_TLS_LDO_32";
		case 33: return "R_386_TLS_IE_32";
		case 34: return "R_386_TLS_LE_32";
		case 35: return "R_386_TLS_DTPMOD32";
		case 36: return "R_386_TLS_DTPOFF32";
		case 37: return "R_386_TLS_TPOFF32";

	default:
		snprintf(s, sizeof(s), "<unkown: %ju>", r);
		return (s);
	}
}

static int
_is_absolute_reloc(uint64_t r)
{

	if (r == R_386_32)
		return (1);

	return (0);
}

static int
_is_relative_reloc(uint64_t r)
{

	if (r == R_386_RELATIVE)
		return (1);

	return (0);
}

static void
_warn_pic(struct ld *ld, struct ld_reloc_entry *lre)
{
	struct ld_symbol *lsb;

	lsb = lre->lre_sym;

	if (lsb->lsb_bind != STB_LOCAL)
		ld_warn(ld, "relocation %s against `%s' can not be used"
		    " by runtime linker; recompile with -fPIC",
		    _reloc2str(lre->lre_type), lsb->lsb_name);
	else
		ld_warn(ld, "relocation %s can not be used by runtime linker;"
		    " recompile with -fPIC", _reloc2str(lre->lre_type));
}

static struct ld_input_section *
_find_and_create_got_section(struct ld *ld, int create)
{
	struct ld_input_section *is;

	/* Check if the GOT section is already created. */
	is = ld_input_find_internal_section(ld, ".got");
	if (is != NULL)
		return (is);

	if (create) {
		is = ld_input_add_internal_section(ld, ".got");
		is->is_entsize = 4;
		is->is_align = 4;
		is->is_type = SHT_PROGBITS;
		is->is_flags = SHF_ALLOC | SHF_WRITE;
	}

	return (is);
}

static struct ld_input_section *
_find_and_create_gotplt_section(struct ld *ld, int create)
{
	struct ld_input_section *is;

	/* Check if the GOT (for PLT) section is already created. */
	is = ld_input_find_internal_section(ld, ".got.plt");
	if (is != NULL)
		return (is);

	if (create) {
		is = ld_input_add_internal_section(ld, ".got.plt");
		is->is_entsize = 4;
		is->is_align = 4;
		is->is_type = SHT_PROGBITS;
		is->is_flags = SHF_ALLOC | SHF_WRITE;

		/* Reserve space for the initial entries. */
		(void) ld_input_reserve_ibuf(is, 3);

		/* Create _GLOBAL_OFFSET_TABLE_ symbol. */
		ld_symbols_add_internal(ld, "_GLOBAL_OFFSET_TABLE_", 0, 0,
		    is->is_index, STB_LOCAL, STT_OBJECT, STV_HIDDEN, is, NULL);
	}

	return (is);
}

static struct ld_input_section *
_find_and_create_plt_section(struct ld *ld, int create)
{
	struct ld_input_section *is;

	/* Check if the PLT section is already created. */
	is = ld_input_find_internal_section(ld, ".plt");
	if (is != NULL)
		return (is);

	if (create) {
		is = ld_input_add_internal_section(ld, ".plt");
		is->is_entsize = 4;
		is->is_align = 4;
		is->is_type = SHT_PROGBITS;
		is->is_flags = SHF_ALLOC | SHF_EXECINSTR;

		/* Reserve space for the initial entry. */
		(void) ld_input_reserve_ibuf(is, 1);
	}

	return (is);
}

static void
_reserve_got_entry(struct ld *ld, struct ld_symbol *lsb, int num)
{
	struct ld_input_section *is;

	is = _find_and_create_got_section(ld, 1);

	/* Check if the entry already has a GOT entry. */
	if (lsb->lsb_got)
		return;

	/* Reserve GOT entries. */
	lsb->lsb_got_off = ld_input_reserve_ibuf(is, num);
	lsb->lsb_got = 1;
}

static void
_reserve_gotplt_entry(struct ld *ld, struct ld_symbol *lsb)
{
	struct ld_input_section *is;
	uint64_t off;

	is = _find_and_create_gotplt_section(ld, 1);

	/* Reserve a GOT entry for PLT. */
	off = ld_input_reserve_ibuf(is, 1);

	/*
	 * Record a R_386_JMP_SLOT entry for this symbol. Note that
	 * we don't need to record the offset (relative to the GOT section)
	 * here, since the PLT relocations will be sorted later and we
	 * will generate GOT section according to the new order.
	 */
	_create_plt_reloc(ld, lsb, 0);
}

static void
_reserve_plt_entry(struct ld *ld, struct ld_symbol *lsb)
{
	struct ld_input_section *is;

	is = _find_and_create_plt_section(ld, 1);

	(void) ld_input_reserve_ibuf(is, 1);
	lsb->lsb_plt = 1;
}

static void
_create_plt_reloc(struct ld *ld, struct ld_symbol *lsb, uint64_t offset)
{

	ld_reloc_create_entry(ld, ".rel.plt", NULL, R_386_JMP_SLOT,
	    lsb, offset, 0);

	lsb->lsb_dynrel = 1;
}

static void
_create_got_reloc(struct ld *ld, struct ld_symbol *lsb, uint64_t type,
    uint64_t offset)
{
	struct ld_input_section *tis;

	tis = _find_and_create_got_section(ld, 0);
	assert(tis != NULL);

	ld_reloc_create_entry(ld, ".rel.got", tis, type, lsb, offset, 0);

	if (type != R_386_RELATIVE)
		lsb->lsb_dynrel = 1;
}

static void
_create_copy_reloc(struct ld *ld, struct ld_symbol *lsb)
{
	struct ld_input_section *tis;

	ld_dynamic_reserve_dynbss_entry(ld, lsb);

	tis = ld_input_find_internal_section(ld, ".dynbss");
	assert(tis != NULL);

	ld_reloc_create_entry(ld, ".rel.bss", tis, R_386_COPY, lsb,
	    lsb->lsb_value, 0);

	lsb->lsb_dynrel = 1;
}

static void
_create_dynamic_reloc(struct ld *ld, struct ld_input_section *is,
    struct ld_symbol *lsb, uint64_t type, uint64_t offset)
{

	if (lsb->lsb_bind == STB_LOCAL) {
		if (is->is_flags & SHF_WRITE)
			ld_reloc_create_entry(ld, ".rel.data.rel.local",
			    is, type, lsb, offset, 0);
		else
			ld_reloc_create_entry(ld, ".rel.data.rel.ro.local",
			    is, type, lsb, offset, 0);
	} else {
		if (is->is_flags & SHF_WRITE)
			ld_reloc_create_entry(ld, ".rel.data.rel",
			    is, type, lsb, offset, 0);
		else
			ld_reloc_create_entry(ld, ".rel.data.rel.ro",
			    is, type, lsb, offset, 0);
	}

	if (type != R_386_RELATIVE)
		lsb->lsb_dynrel = 1;
}

static void
_scan_reloc(struct ld *ld, struct ld_input_section *is,
    struct ld_reloc_entry *lre)
{
	struct ld_symbol *lsb;

	lsb = ld_symbols_ref(lre->lre_sym);

	switch (lre->lre_type) {
	case R_386_NONE:
		break;

	case R_386_32:
		/*
		 * For a local symbol, if te linker output a PIE or DSO,
		 * we should generate a R_386_RELATIVE reloc for R_386_32.
		 */
		if (lsb->lsb_bind == STB_LOCAL) {
			if (ld->ld_pie || ld->ld_dso)
				_create_dynamic_reloc(ld, is, lsb,
				    R_386_RELATIVE, lre->lre_offset);
			break;
		}

		/*
		 * For a global symbol, we probably need to generate PLE entry
		 * and/ore a dynamic relocation.
		 *
		 * Note here, normally the compiler will generate a PC-relative
		 * relocation for function calls. However, if the code retrieve
		 * the address of a function and call it indirectly, assembler
		 * will generate absolute relocation instead. That's why we
		 * should check if we need to create a PLT entry here. Also, if
		 * we're going to create the PLT entry, we should also set the
		 * symbol value to the address of PLT entry just in case the
		 * function address is used to compare with other function
		 * addresses. (If PLT address is used, function will have
		 * unified address in the main executable and DSOs)
		 */
		if (ld_reloc_require_plt(ld, lre)) {
			if (!lsb->lsb_plt) {
				_reserve_gotplt_entry(ld, lsb);
				_reserve_plt_entry(ld, lsb);
			}
			/*
			 * Note here even if we have generated PLT for this
			 * function before, we still need to set this flag.
			 * It's possible that we first see the relative
			 * relocation then this absolute relocation, in
			 * other words, the same function can be called in
			 * different ways.
			 */
			lsb->lsb_func_addr = 1;
		}

		if (ld_reloc_require_copy_reloc(ld, lre) &&
		    !lsb->lsb_copy_reloc)
			_create_copy_reloc(ld, lsb);
		else if (ld_reloc_require_dynamic_reloc(ld, lre)) {
			/*
			 * Check if we can relax R_386_32 to
			 * R_386_RELATIVE instead.
			 */
			if (ld_reloc_relative_relax(ld, lre))
				_create_dynamic_reloc(ld, is, lsb,
				    R_386_RELATIVE, lre->lre_offset);
			else
				_create_dynamic_reloc(ld, is, lsb,
				    R_386_32, lre->lre_offset);
		}

		break;

	case R_386_PLT32:
		/*
		 * In some cases we don't really need to generate a PLT
		 * entry, then a R_386_PLT32 relocation can be relaxed
		 * to a R_386_PC32 relocation.
		 */
		if (lsb->lsb_bind == STB_LOCAL ||
		    !ld_reloc_require_plt(ld, lre)) {
			lre->lre_type = R_386_PC32;
			break;
		}

		/*
		 * If linker outputs an normal executable and the symbol is
		 * defined but is not defined inside a DSO, we can generate
		 * a R_386_PC32 relocation instead.
		 */
		if (ld->ld_exec && lsb->lsb_shndx != SHN_UNDEF &&
		    (lsb->lsb_input == NULL ||
		    lsb->lsb_input->li_type != LIT_DSO)) {
			lre->lre_type = R_386_PC32;
			break;
		}

		/* Create an PLT entry otherwise. */
		if (!lsb->lsb_plt) {
			_reserve_gotplt_entry(ld, lsb);
			_reserve_plt_entry(ld, lsb);
		}
		break;

	case R_386_PC32:
		/*
		 * When R_386_PC32 apply to a global symbol, we should
		 * check if we need to generate PLT entry and/or a dynamic
		 * relocation.
		 */
		if (lsb->lsb_bind != STB_LOCAL) {
			if (ld_reloc_require_plt(ld, lre) && !lsb->lsb_plt) {
				_reserve_gotplt_entry(ld, lsb);
				_reserve_plt_entry(ld, lsb);
			}

			if (ld_reloc_require_copy_reloc(ld, lre) &&
			    !lsb->lsb_copy_reloc)
				_create_copy_reloc(ld, lsb);
			else if (ld_reloc_require_dynamic_reloc(ld, lre)) {
				/*
				 * We can not generate dynamic relocation for
				 * these PC-relative relocation since they
				 * are probably not supported by the runtime
				 * linkers.
				 */
				_warn_pic(ld, lre);
			}
		}
		break;

	case R_386_GOTOFF:
	case R_386_GOTPC:
		/*
		 * These relocation types use GOT address as a base address
		 * and instruct the linker to build a GOT.
		 */
		(void) _find_and_create_got_section(ld, 1);
		break;

	case R_386_GOT32:
		/*
		 * R_386_GOT32 relocation instructs the linker to build a
		 * GOT and generate a GOT entry.
		 */
		if (!lsb->lsb_got) {
			_reserve_got_entry(ld, lsb, 1);
			/*
			 * TODO: For now we always create a R_386_GLOB_DAT
			 * relocation for a GOT entry. There are cases that
			 * the symbol's address is known at link time and
			 * the GOT entry value can be filled in by the program
			 * linker instead.
			 */
			if (ld_reloc_require_glob_dat(ld, lre))
				_create_got_reloc(ld, lsb, R_386_GLOB_DAT,
				    lsb->lsb_got_off);
			else
				_create_got_reloc(ld, lsb, R_386_RELATIVE,
				    lsb->lsb_got_off);
		}

	default:
		ld_warn(ld, "can not handle relocation %ju",
		    lre->lre_type);
		break;
	}
}

static uint32_t
_got_offset(struct ld *ld, struct ld_symbol *lsb)
{
	struct ld_output_section *os;

	assert(lsb->lsb_got);

	if (ld->ld_got == NULL) {
		ld->ld_got = _find_and_create_got_section(ld, 0);
		assert(ld->ld_got != NULL);
	}

	os = ld->ld_got->is_output;

	return (os->os_addr + ld->ld_got->is_reloff + lsb->lsb_got_off);
}

static void
_process_reloc(struct ld *ld, struct ld_input_section *is,
    struct ld_reloc_entry *lre, struct ld_symbol *lsb, uint8_t *buf)
{
	struct ld_state *ls;
	struct ld_output *lo;
	uint32_t p, s, l, g, got;
	int32_t a, v;

	ls = &ld->ld_state;

	lo = ld->ld_output;
	assert(lo != NULL);

	l = lsb->lsb_plt_off;
	p = lre->lre_offset + is->is_output->os_addr + is->is_reloff;
	got = ld->ld_got->is_output->os_addr;
	s = (uint32_t) lsb->lsb_value;
	READ_32(buf + lre->lre_offset, a);

	switch (lre->lre_type) {
	case R_386_NONE:
		break;

	case R_386_32:
		v = s + a;
		WRITE_32(buf + lre->lre_offset, v);
		break;

	case R_386_PC32:
		if (lsb->lsb_plt)
			v = l + a - p;
		else
			v = s + a - p;
		WRITE_32(buf + lre->lre_offset, v);
		break;

	case R_386_PLT32:
		if (!ls->ls_ignore_next_plt) {
			v = l + a - p;
			WRITE_32(buf + lre->lre_offset, v);
		} else
			ls->ls_ignore_next_plt = 0;
		break;

	case R_386_GOT32:
		g = _got_offset(ld, lsb);
		v = g + a;
		WRITE_32(buf + lre->lre_offset, v);
		break;

	case R_386_GOTOFF:
		v = s + a - got;
		WRITE_32(buf + lre->lre_offset, v);
		break;

	case R_386_GOTPC:
		v = got + a - p;
		WRITE_32(buf + lre->lre_offset, v);
		break;

	default:
		ld_fatal(ld, "Relocation %d not supported", lre->lre_type);
		break;
	}
}

void
i386_register(struct ld *ld)
{
	struct ld_arch *i386_arch;

	if ((i386_arch = calloc(1, sizeof(*i386_arch))) == NULL)
		ld_fatal_std(ld, "calloc");
	
	snprintf(i386_arch->name, sizeof(i386_arch->name), "%s", "i386");

	i386_arch->script = i386_script;
	i386_arch->get_max_page_size = _get_max_page_size;
	i386_arch->get_common_page_size = _get_common_page_size;
	i386_arch->scan_reloc = _scan_reloc;
	i386_arch->process_reloc = _process_reloc;
	i386_arch->is_absolute_reloc = _is_absolute_reloc;
	i386_arch->is_relative_reloc = _is_relative_reloc;
	i386_arch->reloc_is_64bit = 0;
	i386_arch->reloc_is_rela = 0;
	i386_arch->reloc_entsize = sizeof(Elf32_Rel);

	HASH_ADD_STR(ld->ld_arch_list, name, i386_arch);
}
